﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.WebPartPages;
using WebPart = System.Web.UI.WebControls.WebParts.WebPart;

namespace TagCloud.TagCloud
{
	[Guid("5e6a5492-4886-4a9f-98f5-dba858b4f34a")]
	public class Cloud : WebPart
	{
		#region Private Fields

		private readonly Random rnd = new Random(DateTime.Now.Millisecond);
		private readonly Dictionary<string, Tag> Tags = new Dictionary<string, Tag>();
		private string documentLibraryType = "1";
		private string listType = "0";
		private bool openItemsInNewWindows;
		private Enumerations.SearchScopes scope = Enumerations.SearchScopes.SiteCollection;
		private char separator = ',';
		private string tagField = "Tags";
		private string targetUrl = "/_layouts/OSSSearchResults.aspx?k=";
		private Boolean isActivatedCache = true;

		#endregion

		#region Public Fields

		/// <summary>
		/// Gets or sets the tag field.
		/// </summary>
		/// <value>The tag field.</value>
		[WebBrowsable(true)]
		[Category("Parametri")]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Personalizable(PersonalizationScope.Shared)]
		[FriendlyName("Nome colonne Tag da ricercare")]
		[Description("Imposta il nome delle colonne Tag su cui effettuare la ricerca")]
		public string TagField
		{
			get { return tagField; }
			set { tagField = value; }
		}

		/// <summary>
		/// Gets or sets the type of the list.
		/// </summary>
		/// <value>The type of the list.</value>
		public string ListType
		{
			get { return listType; }
			set { listType = value; }
		}

		/// <summary>
		/// Gets or sets the type of the document library.
		/// </summary>
		/// <value>The type of the document library.</value>
		public string DocumentLibraryType
		{
			get { return documentLibraryType; }
			set { documentLibraryType = value; }
		}

		/// <summary>
		/// Gets or sets the scope.
		/// </summary>
		/// <value>The scope.</value>
		[WebBrowsable(true)]
		[Category("Parametri")]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Personalizable(PersonalizationScope.Shared)]
		[FriendlyName("Ambito di ricerca")]
		[Description("Imposta l'ambito di ricerca per i Tag: solo il sito corrente oppure l'intera site collection")]
		public Enumerations.SearchScopes Scope
		{
			get { return scope; }
			set { scope = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether [open items in new windows].
		/// </summary>
		/// <value>
		/// 	<c>true</c> if [open items in new windows]; otherwise, <c>false</c>.
		/// </value>
		[WebBrowsable(true)]
		[Category("Parametri")]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Personalizable(PersonalizationScope.Shared)]
		[FriendlyName("Apri i link in una nuova finestra")]
		[Description("Se selezionata permette di aprire tutti i link di un Tag in nuove finestre")]
		public bool OpenItemsInNewWindows
		{
			get { return openItemsInNewWindows; }
			set { openItemsInNewWindows = value; }
		}

		/// <summary>
		/// Gets or sets the separator.
		/// </summary>
		/// <value>The separator.</value>
		[WebBrowsable(true)]
		[Category("Parametri")]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Personalizable(PersonalizationScope.Shared)]
		[FriendlyName("Carattere separatore per i Tag")]
		[Description("Imposta il valore del carattere separatore per i Tag")]
		public char Separator
		{
			get { return separator; }
			set { separator = value; }
		}

		/// <summary>
		/// Gets or sets the target URL.
		/// </summary>
		/// <value>The target URL.</value>
		[WebBrowsable(true)]
		[Category("Parametri")]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Personalizable(PersonalizationScope.Shared)]
		[FriendlyName("Url della pagina di ricerca per i Tag")]
		[Description("Imposta l'Url della pagina di ricerca per i Tag")]
		public string TargetUrl
		{
			get { return targetUrl; }
			set { targetUrl = value; }
		}

		[WebBrowsable(true)]
		[Category("Parametri")]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Personalizable(PersonalizationScope.Shared)]
		[FriendlyName("Attiva Cache")]
		[Description("Flag per l'attivazione della cache")]
		public bool IsActivatedCache
		{
			get { return isActivatedCache; }
			set { isActivatedCache = value; }
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
		/// </summary>
		protected override void CreateChildControls()
		{
			base.CreateChildControls();

			BuildWebPart();
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Builds the web part.
		/// </summary>
		private void BuildWebPart()
		{
			try
			{
				AddTags(ListType, Scope);
				AddTags(DocumentLibraryType, Scope);

				Table table = new Table();
				table.Width = new Unit(100, UnitType.Percentage);
				TableRow row = new TableRow();
				table.Rows.Add(row);
				TableCell cell = new TableCell();
				cell.HorizontalAlign = HorizontalAlign.Center;
				row.Cells.Add(cell);
				Controls.Add(table);

				foreach (KeyValuePair<string, Tag> tag in Tags)
				{
					LinkButton Tag = new LinkButton();
					Tag.Text = tag.Value.Title;
					int count = tag.Value.Links.Count;
					Tag.ToolTip = string.Format("{0} occorrenze", count);
					Tag.Font.Size = new FontUnit(GetFontSize(count), UnitType.Point);
					Tag.ForeColor = GetRandomColor();
					Tag.CommandArgument = tag.Key;
					Tag.Click += Tag_Click;
					cell.Controls.Add(Tag);
					Literal lt = new Literal();
					lt.Text = " ";
					cell.Controls.Add(lt);
				}
			}
			catch (Exception ex)
			{
				SetErrorMessage(ex);
			}
		}

		/// <summary>
		/// Gets the random color.
		/// </summary>
		/// <returns></returns>
		private Color GetRandomColor()
		{
			Color result = Color.Black;
			try
			{
				int red = rnd.Next(0, 255);
				int green = rnd.Next(0, 255);
				int blue = rnd.Next(0, 255);
				result = Color.FromArgb(red, green, blue);
			}
			catch
			{
			}
			return result;
		}

		/// <summary>
		/// Adds the links.
		/// </summary>
		/// <param name="key">The key.</param>
		private void AddLinks(string key)
		{
			Tag tag = Tags[key];
			AddSeparator();
			Table linksTable = new Table();
			linksTable.Width = new Unit(100, UnitType.Percentage);
			//linksTable.Style.Add(HtmlTextWriterStyle.ZIndex, 1.ToString());
			//linksTable.Style.Add(HtmlTextWriterStyle.Top, 0.ToString());
			foreach (TagLink link in tag.Links)
			{
				TableRow row = new TableRow();
				linksTable.Rows.Add(row);
				TableCell cell = new TableCell();
				row.Cells.Add(cell);
				HyperLink hLink = new HyperLink();
				hLink.Text = link.Title;
				hLink.NavigateUrl = link.ItemUrl;
				cell.Controls.Add(hLink);
			}
			Controls.Add(linksTable);
		}

		/// <summary>
		/// Adds the separator.
		/// </summary>
		private void AddSeparator()
		{
			Table table = new Table();
			table.Width = new Unit(100, UnitType.Percentage);
			TableRow row = new TableRow();
			table.Rows.Add(row);
			TableCell cell = new TableCell();
			cell.HorizontalAlign = HorizontalAlign.Center;
			row.Cells.Add(cell);
			Label lbl = new Label();
			lbl.Text = "----------------";
			cell.Controls.Add(lbl);
			Controls.Add(table);
		}

		/// <summary>
		/// Adds the tags.
		/// </summary>
		/// <param name="listType">Type of the list.</param>
		/// <param name="searchScope">The search scope.</param>
		private void AddTags(string listType, Enumerations.SearchScopes searchScope)
		{
			DataTable results = GetResults(listType, searchScope);
			GetTags(results);
		}

		/// <summary>
		/// Gets the results.
		/// </summary>
		/// <param name="listType">Type of the list.</param>
		/// <param name="searchScope">The search scope.</param>
		/// <returns></returns>
		private DataTable GetResults(string listType, Enumerations.SearchScopes searchScope)
		{
			string lists = "<Lists BaseType=\"" + listType + "\" MaxListLimit='0'/>";
			string viewFields =
				string.Format(
					"<FieldRef Name=\"ID\" /><FieldRef Name=\"{0}\" /><FieldRef Name=\"FileRef\" /><FieldRef Name=\"Title\" />",
					TagField);
			string webs = "<Webs Scope=\"" + Enum.GetName(typeof(Enumerations.SearchScopes), searchScope) + "\" />";

			DataTable results;

			if (!isActivatedCache)
			{
				SPSiteDataQuery siteQuery = new SPSiteDataQuery();
				siteQuery.Lists = lists;
				siteQuery.ViewFields = viewFields;
				siteQuery.Webs = webs;
				//siteQuery.RowLimit = 0;
				results = SPContext.Current.Web.GetSiteData(siteQuery);
			}
			else
			{
				CrossListQueryInfo query = new CrossListQueryInfo();

				query.WebUrl = SPContext.Current.Site.ServerRelativeUrl;
				query.Lists = lists;
				query.Webs = webs;
				query.Query = string.Format("<Where><IsNotNull><FieldRef Name=\"{0}\" /></IsNotNull></Where>", TagField);
				query.ViewFields = viewFields;
				CrossListQueryCache cache = new CrossListQueryCache(query);
				results = cache.GetSiteData(SPContext.Current.Site);
			}

			return results;
		}

		/// <summary>
		/// Handles the Click event of the Tag control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void Tag_Click(object sender, EventArgs e)
		{
			LinkButton button = sender as LinkButton;
			if (button != null)
			{
				if (string.IsNullOrEmpty(TargetUrl))
					AddLinks(button.CommandArgument);
				else Page.Response.Redirect(TargetUrl + button.CommandArgument);
			}
		}

		/// <summary>
		/// Gets the size of the font.
		/// </summary>
		/// <param name="count">The count.</param>
		/// <returns></returns>
		private static double GetFontSize(int count)
		{
			double result = 8;
			switch (count)
			{
				case 2:
					result = 10;
					break;
				case 3:
					result = 12;
					break;
				case 4:
					result = 14;
					break;
				case 5:
					result = 16;
					break;
				case 6:
					result = 18;
					break;
				case 7:
					result = 20;
					break;
				case 8:
					result = 22;
					break;
				case 9:
					result = 24;
					break;
			}
			if (count > 10)
				result = 26;

			return result;
		}

		/// <summary>
		/// Gets the tags.
		/// </summary>
		/// <param name="results">The results.</param>
		private void GetTags(DataTable results)
		{
			foreach (DataRow row in results.Rows)
			{
				string[] localTags = row[3].ToString().Split(Separator);
				foreach (string tag in localTags)
					GetTag(row, tag.Trim());
			}
		}

		/// <summary>
		/// Gets the tag.
		/// </summary>
		/// <param name="row">The row.</param>
		/// <param name="tag">The tag.</param>
		private void GetTag(DataRow row, string tag)
		{
			if (Tags.ContainsKey(tag))
				UpdateTag(row, tag);
			else AddTag(row, tag);
		}

		/// <summary>
		/// Updates the tag.
		/// </summary>
		/// <param name="row">The row.</param>
		/// <param name="localTag">The local tag.</param>
		private void UpdateTag(DataRow row, string localTag)
		{
			Tag tag = Tags[localTag];
			SaveTag(tag, row);
		}

		/// <summary>
		/// Adds the tag.
		/// </summary>
		/// <param name="row">The row.</param>
		/// <param name="localTag">The local tag.</param>
		private void AddTag(DataRow row, string localTag)
		{
			Tag newTag = new Tag();
			newTag.Title = localTag;
			SaveTag(newTag, row);
			Tags.Add(localTag, newTag);
		}

		/// <summary>
		/// Saves the tag.
		/// </summary>
		/// <param name="newTag">The new tag.</param>
		/// <param name="row">The row.</param>
		private static void SaveTag(Tag newTag, DataRow row)
		{
			TagLink link = new TagLink();
			link.ListID = new Guid(row[0].ToString());
			link.WebID = new Guid(row[1].ToString());
			link.ItemUrl = GetItemUrl(row[4].ToString());
			link.Title = row[5].ToString();
			newTag.Links.Add(link);
		}

		/// <summary>
		/// Gets the item URL.
		/// </summary>
		/// <param name="s">The s.</param>
		/// <returns></returns>
		private static string GetItemUrl(string s)
		{
			string result = string.Empty;
			try
			{
				string split = s.Split('#')[1];
				string remove = split.Remove(split.LastIndexOf('/'));
				string substring = s.Substring(s.LastIndexOf('/') + 1);
				string remove1 = substring.Remove(substring.LastIndexOf('_'));
				result = SPContext.Current.Web.Site.Url + "/" + remove + "/DispForm.aspx?ID=" + remove1;
			}
			catch (Exception ex)
			{
			}
			return result;
		}

		/// <summary>
		/// Sets the error message.
		/// </summary>
		/// <param name="ex">The ex.</param>
		private void SetErrorMessage(Exception ex)
		{
			Controls.Clear();
			Label errorMessage = new Label();
			errorMessage.Text = GetErrorMessage(ex);
			errorMessage.CssClass = "ms-formvalidation";
			Controls.Add(errorMessage);
		}

		/// <summary>
		/// Gets the error message.
		/// </summary>
		/// <param name="ex">The ex.</param>
		/// <returns></returns>
		private static string GetErrorMessage(Exception ex)
		{
			StringBuilder result = new StringBuilder();
			try
			{
				StackTrace st = new StackTrace(ex, true);

				result.AppendLine(string.Format(" Stack trace for current level: {0}", st));

				StackFrame[] frames = st.GetFrames();
				if (frames != null)
					foreach (StackFrame sf in frames)
					{
						result.AppendLine(string.Format(" File: {0}", sf.GetFileName()));
						result.AppendLine(string.Format(" Method: {0}", sf.GetMethod().Name));
						result.AppendLine(string.Format(" Line Number: {0}", sf.GetFileLineNumber()));
						result.AppendLine(string.Format(" Column Number: {0}", sf.GetFileColumnNumber()));
					}
			}
			catch
			{
			}
			return result.ToString();
		}

		#endregion
	}
}